<?php
/**
 * Copyright (c) 2006-2008, Julien PORTALIER
 * http://featherphp.googlecode.com/
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 */

/**
 * MySQL backend.
 */
class db_mysql extends db_base
{
	protected $operators   = '=|<|>|<=|>=|<>|<=>|LIKE|REGEXP|BETWEEN';
	protected $field_quote = '`';
	
	function start()
	{
		# ignition
		if ($this->conf['permanent'])
		{
			if (false === ($this->link = mysql_pconnect($this->conf['host'], $this->conf['login'], $this->conf['password']))) {
				throw new Exception("Can't connect to MySQL server.", DBO::ErrorConnect);
			}
		}
		else
		{
			# forces creation of new link (just in case)
			if (false === ($this->link = mysql_connect($this->conf['host'], $this->conf['login'], $this->conf['password'], true))) {
				throw new Exception("Can't connect to MySQL server.", DBO::ErrorConnect);
			}
		}
		
		# schema to use
		if (!empty($this->conf['database'])) {
			$this->use_database($this->conf['database']);
		}
		
		# unicode?
		if ($this->conf['unicode']) {
			$this->execute("SET NAMES 'UTF8' ;");
		}
	}
	
	function stop()
	{
		if ($this->link)
		{
			mysql_close($this->link);
			$this->link = false;
		}
	}
	
	function use_database($database=null)
	{
		if (!mysql_select_db($database, $this->link)) {
			throw new Exception("No such database: $database.", DBO::MissingDatabase);
		}
	}
	
	function escape($v)
	{
		return mysql_real_escape_string($v, $this->link);
	}
	
	function execute($sql)
	{
		# connects to server 
		if (!$this->link) {
			$this->start();
		}
		
		# parses SQL
		if (is_array($sql))
		{
			$sql = $this->generate_sql($sql);
		}
		elseif (func_num_args() > 1)
		{
			$args = func_get_args();
			$sql  = call_user_func_array(array($this, 'parse_sql_with_parameters'), $args);
		}
		
		# executes request & time it
		$_start = microtime(true);
		$rs     = mysql_query($sql, $this->link);
		$_end   = microtime(true);
		
		if (!$rs)
		{
			#. error
			$this->error($sql);
		}
		else if (DEBUG < 2)
		{
			#. slow request?
			if ($_end - $_start >= DBO::SLOW_QUERY and function_exists('db_log_slow_query')) {
				db_log_slow_query($sql, $_end - $_start, $this->ds);
			}
		}
		else
		{
			#. debug
			$this->debug($sql, $_end - $_start);
		}
		return $rs;
	}
	
	protected function & parse_query($rs, $process=DBO::PROCESS_FULL)
	{
		# parsing data
		$data = array();
		if ($rs and mysql_num_rows($rs) > 0)
		{
			switch($process)
			{
				# data[table][field] = value
				case DBO::PROCESS_FULL:
					while ($row = mysql_fetch_row($rs))
					{
						$r = array();
						foreach ($row as $idx => $v)
						{
							$table = mysql_field_table($rs, $idx);
							$r[$table][mysql_field_name($rs, $idx)] = $v;
						}
						array_push($data, $r);
					}
				break;
				
				# data[field] = value
				case DBO::PROCESS_LITE:
					while ($row = mysql_fetch_assoc($rs)) {
						array_push($data, $row);
					}
				break;
				
				# data = value
				case DBO::PROCESS_VALUE:
					$row = mysql_fetch_row($rs);
					$data = $row[0];
				break;
				
				# data[index] = value
				case DBO::PROCESS_LIST:
					while ($row = mysql_fetch_row($rs)) {
						array_push($data, $row[0]);
					}
				break;
				
				# data[key] = value
				case DBO::PROCESS_ASSOC_LIST:
					while ($row = mysql_fetch_row($rs)) {
						$data[$row[0]] = $row[1];
					}
				break;
				
				# data[] = value
				case DBO::PROCESS_NONE: default:
					while ($row = mysql_fetch_row($rs)) {
						array_push($data, $row);
					}
				break;
			}
			
			# cleanup
			mysql_free_result($rs);
			unset($this->table_aliases);
		}
		return $data;
	}
	
	function & describe_table($table)
	{
		$fields = array();
		$rs = $this->execute("DESC $table ;");
		
		if (!$rs)
		{
			throw new Exception("No such table: {$table}", DBO::MissingTable);
		}
		
		while ($f = mysql_fetch_row($rs))
		{
			$name = array_shift($f);
			$type = strtolower(array_shift($f));
			
			$fields[$name] = array(
				'type' => $type,
				'null' => (array_shift($f) != 'NO'),
				'key'  => (array_shift($f) == 'PRI') ? true  : false,
			);
			
			if ($type == 'tinytext')
			{
				$fields[$name]['length'] = 255;
			}
			elseif (strpos($type, 'varchar(') === 0)
			{
				$fields[$name]['length'] = str_replace(array('varchar(', ')'), '', $type);
			}
		}
		mysql_free_result($rs);
		return $fields;
	}
	
	
	function limit($limit, $page=1)
	{
		if ($page > 1)
		{
			return "LIMIT ".(($page - 1) * $limit).", ".$limit;
		}
		elseif ($limit)
		{
			return "LIMIT $limit";
		}
	}
	
	
	/**
	 * INSERTS a row.
	 * 
	 * $values  = array('title' => $title, 'body' => $body);
	 * $success = $db->insert('table', $values);
	 * $id      = $db->insert('table', $values, 'id');
	 * 
	 * @return mixed Primary key for inserted row if $key is passed; otherwise success.
	 * @param $table String Name of table. 
	 * @param $data Array Hash of fields => values to insert.
	 * @param $key String
	 * @param $on_duplicate Array[optional]
	 */
	function insert($table, array &$data, $key=null, array &$on_duplicate=null)
	{
		$table  = $this->field($table);
		$fields = $this->fields(array_keys($data));
		$values = $this->values(array_values($data));
		
		if ($on_duplicate !== null)
		{
			$update = array();
			foreach($on_duplicate as $f => $v) {
				$update[] = $this->field($f).' = '.$this->value($v);
			}
			$update = 'ON DUPLICATE KEY UPDATE '.implode(', ', $update);
		}
		else {
			$update = '';
		}
		
		if (!$this->execute("INSERT INTO $table ( $fields ) VALUES ( $values ) $update ;")) {
			return false;
		}
		return is_null($key) ? true : mysql_insert_id($this->link);
	}
	
	/**
	 * INSERTS many rows.
	 * 
	 * \code
	 * $fields  = 'title, body';
	 * $values  = array(
	 *   array($title1, $body1),
	 *   array($title2, $body2),
	 *   array($title3, $body3)
	 * );
	 * $success = $db->insert_many('table', $fields, $values);
	 * \endcode
	 * 
	 * @return Boolean Success?
	 * @param $table String Name of table.
	 * @param $fields Mixed List of fields.
	 * @param $values Array Array of values to insert.
	 * @param $on_duplicate Array[optional]
	 */
	function insert_many($table, array &$fields, array &$data, array &$on_duplicate=null)
	{
		$table   = $this->field($table);
		$_fields = $this->fields($fields);
		$values  = $this->__parse_many_values($data);
		
		if ($on_duplicate !== null)
		{
			$update = array();
			foreach($on_duplicate as $f => $v) {
				$update[] = $this->field($f).' = '.$this->value($v);
			}
			$update = 'ON DUPLICATE KEY UPDATE '.implode(', ', $update);
		}
		else {
			$update = '';
		}
		
		return $this->execute("INSERT INTO $table ( $_fields ) VALUES $values $update ;");
	}
	
	/**
	 * Returns the last created ID.
	 * @deprecated Use $db->insert($table, $values, $auto_increment_key);
	 */
	function get_last_insert_id()
	{
		return mysql_insert_id($this->link);
	}
	
	
	
	protected function error($sql=null)
	{
		if (DEBUG > 1)
		{
			if (!is_null($sql))
			{
				echo '<p class="monospace">'.$sql.'</p>';
			}
			trigger_error("MySQL error ".mysql_errno($this->link).": ".mysql_error($this->link), E_USER_ERROR);
		}
		elseif (function_exists('db_log_error'))
		{
			db_log_error($sql, mysql_errno($this->link), mysql_error($this->link), $this->ds);
		}
		else
		{
			$this->debug($sql);
		}
	}
	
	/**
	 * Record the query (when in SQL DEBUG mode).
	 */
	protected function debug($sql, $time=null)
	{
		$q = array(
			'sql'      => $sql,
			'error'    => mysql_errno($this->link)." ".mysql_error($this->link),
			'affected' => mysql_affected_rows($this->link),
			'time'     => $time
		);
		array_push($this->debugs, $q);
	}
}
?>